package org.bridgen.runtime;

import java.util.ArrayList;
import java.util.Collection;

import org.bridgen.runtime.log.RecoveryLog;
import org.bridgen.runtime.model.AmbNode;
import org.bridgen.runtime.model.EOF;
import org.bridgen.runtime.model.LexicalNode;
import org.bridgen.runtime.model.SOF;
import org.bridgen.runtime.model.Water;
import org.bridgen.runtime.util.Interval;

/**
 * 
 * @author Emma Söderberg <emma add cs.lth.se>
 *
 */

public abstract class AbstractBridgeParser implements IBridgeParser {

	public RecoveryResult recover(String text, RecoveryLog log) {
		SOF sof = tokenize(text);
		/*
		 * System.out.println("AFTER TOKENIZATION:"); StringBuffer buf = new
		 * StringBuffer(); RecoveryHelper.printList(sof, buf, "");
		 * System.out.println(buf);
		 */
		mergeWater(sof);
		/*
		 * System.out.println("AFTER MERGE WATER:"); buf = new StringBuffer();
		 * RecoveryHelper.printList(sof, buf, ""); System.out.println(buf);
		 */
		RecoveryAlgorithm.buildBridges(sof, log);
		RecoveryAlgorithm.recover(sof, log);
		RecoveryHelper.prettyPrint(sof).toString();

		Collection<RecoveryOption> optionList = new ArrayList<RecoveryOption>();
		RecoveryResult result = new RecoveryResult(optionList);
		optionList.add(new RecoveryOption(sof));
		return result;
	}

	public String recover(String text) {
		SOF sof = tokenize(text);
		/*
		 * System.out.println("AFTER TOKENIZATION:"); StringBuffer buf = new
		 * StringBuffer(); RecoveryHelper.printList(sof, buf, "");
		 * System.out.println(buf);
		 */
		mergeWater(sof);
		/*
		 * System.out.println("AFTER MERGE WATER:"); buf = new StringBuffer();
		 * RecoveryHelper.printList(sof, buf, ""); System.out.println(buf);
		 */
		RecoveryAlgorithm.buildBridges(sof);
		RecoveryAlgorithm.recover(sof);
		return RecoveryHelper.prettyPrint(sof).toString();
	}

	protected SOF tokenize(String text) {

		char[] content = text.toCharArray();
		int curPos = 0;
		int lastMatchPos = -1;

		SOF sof = new SOF(new Interval(-1, -1));
		LexicalNode previous = sof;

		while (curPos < content.length) {
			LexicalNode newNode = match(content, curPos, lastMatchPos, previous);
			if (newNode == null) {
				curPos++;
			} else {
				// Fill in the gap between the new node and the previous node
				Interval prevInterval = previous.getInterval();
				Interval newInterval = newNode.getInterval();

				int endOfPrevious = prevInterval.getEnd();
				int startOfNew = newInterval.getStart();
				int endOfNew = newInterval.getEnd();

				int startOfWater = endOfPrevious + 1;
				int endOfWater = startOfNew - 1;
				int lengthOfWater = endOfWater - startOfWater + 1;

				if (lengthOfWater > 0) {
					// There's a gap -> create water
					Interval waterInterval = new Interval(startOfWater,
							endOfWater);
					String waterValue = new String(content, startOfWater,
							lengthOfWater);
					LexicalNode water = createWater(null, waterInterval,
							waterValue);
					// Insert before new and after previous
					RecoveryHelper.insertBefore(water, newNode);
				}
				// Update loop variables
				lastMatchPos = endOfNew;
				curPos = endOfNew + 1;
				previous = newNode;
			}
		}

		// There might be trailing water

		if (lastMatchPos + 1 < curPos) {
			Interval prevInterval = previous.getInterval();
			int endOfPrevious = prevInterval.getEnd();
			int startOfWater = endOfPrevious + 1;
			int endOfWater = content.length - 1;
			Interval waterInterval = new Interval(startOfWater, endOfWater);
			String waterValue = new String(content, startOfWater, endOfWater
					- startOfWater);
			LexicalNode water = createWater(previous, waterInterval, waterValue);
			previous = water;
		}
		EOF eof = new EOF(previous, new Interval(content.length - 1,
				content.length - 1));

		// This is a trick for SGLR bridge parsers. It improves matching by
		// adding
		// an additional indentation layout at the start of the list so that the
		// initial island will have the same indentation as the last island.
		/*
		 * LexicalNode layoutNode = previous.getPreviousOfType(Layout.class); if
		 * (layoutNode != null) { Layout layout = new Layout(null,
		 * sof.getInterval().clone(), "\n" + layoutNode.getValue());
		 * RecoveryHelper.insertAfter(layout, sof); }
		 */
		return sof;
	}

	private LexicalNode createWater(LexicalNode previous,
			Interval waterInterval, String waterValue) {
		return new Water(previous, waterInterval, waterValue);
	}

	protected boolean inIgnoreState = false;

	protected LexicalNode match(char[] content, int curPos, int lastMatchPos,
			LexicalNode previous) {

		// End of text
		if (curPos == content.length) {
			return null;
		}

		LexicalNode ignoreNode = matchIgnore(content, curPos, lastMatchPos,
				previous);
		if (ignoreNode != null) {
			return ignoreNode;
		}

		// otherwise match a node or create water
		return matchNode(content, curPos, lastMatchPos, previous);
	}

	protected String extendMatch(char[] content, int curPos, int startLength,
			String matchRegExp) {
		String value = new String(content, curPos, startLength);
		boolean match = true;
		int offset = startLength;
		while (match && ((curPos + offset) < content.length)) {
			String newValue = new String(content, curPos, 1 + offset);
			if (newValue.matches(matchRegExp)) {
				value = newValue;
				offset++;
			} else {
				match = false;
			}
		}
		return value;
	}

	protected int ignoreUntil(char[] content, int pos, String end) {
		boolean noMatch = true;
		while (noMatch && pos < content.length) {
			if (matchForward(content, pos, end)) {
				return pos + end.length() - 1;
			}
			pos++;
		}
		return pos;
	}

	protected boolean matchForward(char[] content, int curPos,
			String valueToMatch) {
		int length = valueToMatch.length();
		if (length + curPos < content.length) {
			String value = new String(content, curPos, length);
			if (value.equals(valueToMatch)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Merges consecutive water nodes into one water node
	 * 
	 * @param sof
	 *            The start of the list
	 */
	protected void mergeWater(LexicalNode node) {
		while (node != null) {
			LexicalNode previous = node.getPrevious();
			if (node instanceof Water && previous != null
					&& previous instanceof Water) {
				((Water) previous).mergeWith((Water) node);
			} else if (node instanceof AmbNode) {
				for (LexicalNode next : ((AmbNode) node).nextOptions()) {
					mergeWater(next);
				}
			}
			node = node.getNext();
		}
	}

	protected abstract LexicalNode matchIgnore(char[] content, int curPos,
			int lastMatchPos, LexicalNode previous);

	protected abstract LexicalNode matchNode(char[] content, int curPos,
			int lastMatchPos, LexicalNode previous);

}
